package ps.client.net;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;

import ps.net.AddUserContent;
import ps.net.ChangePasswordContent;
import ps.net.ChangeUserRightContent;
import ps.net.ChatContent;
import ps.net.DpsParseContent;
import ps.net.LoginContent;
import ps.net.Packet;
import ps.net.RemoveUserContent;
import ps.net.ServerCmdContent;
import ps.net.TriggerDescContent;
import ps.net.TriggerEventContent;
import ps.server.ServerPacketHandler;
import ps.server.trigger.TriggerEntry;
import ps.util.MD5;



public class ClientPacketHandler extends Thread {
    
    private static final int DISCONNECTED = 1;
    private static final int TRY_TO_LOGIN = 2;
    private static final int TRY_TO_CONNECT = 3;
    private static final int CONNECTED = 4;
    
    int connectState = DISCONNECTED;
    boolean running = true;
    
    byte[] authId = MD5.generateAuthId("Gast", "");
    long lastRecievedMsgTime = 0;

    LinkedList<Packet> recievdPackets = new LinkedList<Packet>();
    
    ServerInfo serverInfo = new ServerInfo(this) {
        protected void fireConnectionLost() {
            connectionLost();
        }
    };
    
    LinkedList<Packet> sentPacketList = new LinkedList<Packet>();
    
    Timer timer = new Timer(true);
    TimerTask lookupTask;
    TimerTask reconnectTask;
    TimerTask loginTask;
    
    Packet acknowledgePacket = new Packet(Packet.TYPE_ACKNOWLEGE);
    
    public ClientPacketHandler() {
        start();
    }
    
    public boolean isConnected() {
        return connectState == CONNECTED;
    }
    
    public boolean isDisconnected() {
        return connectState == DISCONNECTED;
    }
    
    public boolean isTryToLogin() {
        return connectState == TRY_TO_LOGIN;
    }
    
    public boolean isTryToConnect() {
        return connectState == TRY_TO_CONNECT;
    }
    
    private void setAuthId(byte[] authId) {
        this.authId = authId;
    }
    
    public void sendChatMsg(String sender, String reciever, String msg) {
        if (isConnected()) {
            Packet packet = new Packet(Packet.TYPE_CHAT);
            packet.setContent(new ChatContent(sender, reciever, msg));
            serverInfo.send(packet);
        }
    }
    
    public void sendAddUser(String userName, String password) {
        if (isConnected()) {
            Packet packet = new Packet(Packet.TYPE_ADD_USER);
            packet.setContent(new AddUserContent(userName, MD5.generateAuthId(userName, password)));
            serverInfo.send(packet);
        }
    }
    
    public void sendRemoveUser(String userName) {
        if (isConnected()) {
            Packet packet = new Packet(Packet.TYPE_REMOVE_USER);
            packet.setContent(new RemoveUserContent(userName));
            serverInfo.send(packet);
        }
    }
    
    public void sendChangePassword(String userName, String password) {
        if (isConnected()) {
            Packet packet = new Packet(Packet.TYPE_CHANGE_PASSWORD);
            packet.setContent(new ChangePasswordContent(userName, MD5.generateAuthId(userName, password)));
            serverInfo.send(packet);
        }
    }
    
    public void sendChangeUserRight(String userName, int right) {
        if (isConnected()) {
            Packet packet = new Packet(Packet.TYPE_CHANGE_USER_RIGHT);
            packet.setContent(new ChangeUserRightContent(userName, right));
            serverInfo.send(packet);
        }
    }
    
    public void sendTriggerDesc(int cmd, TriggerEntry entry) {
        if (isConnected()) {
            Packet packet = new Packet(Packet.TYPE_TRIGGER_DESC);
            packet.setContent(new TriggerDescContent(cmd, entry));
            serverInfo.send(packet);
        }
    }
    
    public void sendTriggerDesc(int cmd, TriggerEntry[] entries) {
        if (isConnected()) {
            Packet packet = new Packet(Packet.TYPE_TRIGGER_DESC);
            packet.setContent(new TriggerDescContent(cmd, entries));
            serverInfo.send(packet);
        }
    }
    
    public void sendTriggerEvent(int triggerId, String triggerAttrStr) {
        if (isConnected()) {
            Packet packet = new Packet(Packet.TYPE_TRIGGER_EVENT);
            TriggerEventContent triggerEventContent = new TriggerEventContent(triggerId);
            triggerEventContent.setAttrStr(triggerAttrStr);
            packet.setContent(triggerEventContent);
            serverInfo.send(packet);
        }
    }
    
    public void sendServerCmd(int cmd) {
        sendServerCmd(cmd, "");
    }
    
    public void sendServerCmd(int cmd, String param1) {
        if (isConnected()) {
            Packet packet = new Packet(Packet.TYPE_SERVER_CMD);
            packet.setContent(new ServerCmdContent(cmd, param1));
            serverInfo.send(packet);
        }
    }
    
    public void sendServerCmd(int cmd, String param1, String param2) {
        if (isConnected()) {
            Packet packet = new Packet(Packet.TYPE_SERVER_CMD);
            packet.setContent(new ServerCmdContent(cmd, param1, param2));
            serverInfo.send(packet);
        }
    }
    
    public void sendDpsParse(DpsParseContent content) {
        if (isConnected()) {
            Packet packet = new Packet(Packet.TYPE_DPS_PARSE);
            packet.setContent(content);
            serverInfo.send(packet);
        }
    }

    public void addRecievedPacket(Packet packet) {
        synchronized (recievdPackets) {
            recievdPackets.add(packet);
        }
    }
    
    @Override
    public void run() {
        while (running) {
            try {
                Packet packet = null;
                synchronized (recievdPackets) {
                    packet = recievdPackets.poll();
                }
            
                if (packet != null && (isConnected() || isTryToLogin() || isTryToConnect())) {
                    lastRecievedMsgTime = System.currentTimeMillis();
                    
                    if (isTryToLogin() || isTryToConnect()) {
                        connected();
                    }

                    switch (packet.getType()) {
                    
                        case Packet.TYPE_CLIENT_INFO:
                            serverInfo.send(acknowledgePacket);
                            break;
                    
                        case Packet.TYPE_LOGOUT:
                            disconnected();
                            break;
                                
                        case Packet.TYPE_SERVER_CMD:
                            ServerCmdContent serverCmdCont = (ServerCmdContent)packet.getContent();
                            switch (serverCmdCont.getCommand()) {
                                case ServerCmdContent.CMD_SHOW_ADMIN_TOOLS:
                                    //
                                    break;
                                case ServerCmdContent.CMD_HIDE_ADMIN_TOOLS:
                                    //
                                    break;
                            }
                            break;
                            
                    }
            
                    firePacketRecieved(packet);
                } else {
                    sleep(1);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    
    
    private void connected() {
        System.out.println("connected");
        
        stopLoginTask();
        stopReconnect();
        stopLookupTask();
        
        connectState = CONNECTED;
        
        lookupTask = new TimerTask() {
            @Override
            public void run() {
                if ((System.currentTimeMillis() - lastRecievedMsgTime) > 30000) {
                    connectionLost();
                    cancel();
                }
            }
        };
        timer.schedule(lookupTask, 0, 5000);
        
        fireConnectionEstablished();
    }
    
    private void disconnected() {
        System.out.println("disconnected");
        
        stopLoginTask();
        stopReconnect();
        stopLookupTask();
        
        connectState = DISCONNECTED;
        serverInfo.disconnect();
        
        fireDisconnected();
    }
    
    private void connectionLost() {
        System.out.println("connection lost");
        
        if (isTryToLogin()) {
            stopLoginTask();
            connectState = DISCONNECTED;
            fireLoginFailed();
        } else {
            connectState = TRY_TO_CONNECT;
            
            fireConnectionLost();
            stopLookupTask();
            stopReconnect();
            reconnectTask = new TimerTask() {
                @Override
                public void run() {
                    reconnect();
                }
            };
            timer.schedule(reconnectTask, 1000, 10000);
        }
    }
    
    private void reconnect() {
        System.out.println("reconnect");
        
        if (!isConnected()) {
            fireTryingToReconnect();
            
            try {
                serverInfo.reconnect(5000);
                
                Packet packet = new Packet(Packet.TYPE_LOGIN);
                packet.setContent(new LoginContent(authId, ServerPacketHandler.VERSION));
                serverInfo.send(packet);
            } catch (IOException ex) {
//                ex.printStackTrace();
            }
        }
    }
    
    public void login(String host, int port, String user, String password) throws IOException {
        System.out.println("login");
        
        stopReconnect();
        stopLookupTask();
        stopLoginTask();
        
        if (isDisconnected()) {
            try {
                serverInfo.connect(new InetSocketAddress(host,  port), 5000);
                
                setAuthId(MD5.generateAuthId(user, password));
                
                Packet packet = new Packet(Packet.TYPE_LOGIN);
                packet.setContent(new LoginContent(authId, ServerPacketHandler.VERSION));
                connectState = TRY_TO_LOGIN;
                serverInfo.send(packet);
                
                loginTask = new TimerTask() {
                    @Override
                    public void run() {
                        connectState = DISCONNECTED;
                        fireLoginFailed();
                        cancel();
                    }
                };
                timer.schedule(loginTask, 3000);
                
            } catch (Exception ex) {
                connectState = DISCONNECTED;
                fireLoginFailed();
            }
        }
    }
    
    public void logout() {
        System.out.println("logout");
        
        stopReconnect();
        stopLookupTask();
        stopLoginTask();
        
        if (serverInfo != null) {
        
            if (isConnected()) {
                serverInfo.send(new Packet(Packet.TYPE_LOGOUT));
            }
        
            serverInfo.disconnect();
        }
        
        connectState = DISCONNECTED;
        
        fireDisconnected();
    }
    
    private void stopLookupTask() {
        try {
            lookupTask.cancel();
        } catch (Exception ex) {
        }
    }
    
    private void stopLoginTask() {
        try {
            loginTask.cancel();
        } catch (Exception ex) {
        }
    }
    
    private void stopReconnect() {
        try {
            reconnectTask.cancel();
        } catch (Exception ex) {
        }
    }
    
    public void close() {
        System.out.println("close");
        
        try {
            timer.cancel();
        } catch (Exception ex) {
        }
        
        serverInfo.close();
    }
    
    protected void fireConnectionEstablished() {
    }
    protected void fireConnectionLost() {
    }
    protected void fireDisconnected() {
    }
    protected void fireTryingToReconnect() {
    }
    protected void fireLoginFailed() {
    }
    protected void firePacketRecieved(Packet packet) {
    }
}
